import org.acegisecurity.annotation.Secured
import org.acegisecurity.annotation.SecurityAnnotationAttributes
import org.acegisecurity.intercept.method.MethodDefinitionAttributes

class AcegiGrailsPlugin {

    def DEFAULT_CONFIG_FILE                 = "DefaultAcegiConfig"
    def APP_CONFIG_FILE                     = "AcegiConfig"
    def AUTHENTICATION_FILTER_PROCESSES_URL = "/j_acegi_security_check"
    def CONTROLLER_SUFFIX                   = "Controller"

    def version = "0.2"
    def author = "Tsuyoshi Yamamoto"
    def authorEmail = "tyama@xmldo.jp"
    def title = "Spring Security (Acegi Security) on Grails Plugin"
    def description = '''Plugin to use grails domain class from the Spring Security(Acegi Security) and secure your applications with Spring Security(Acegi Security) filters.
	'''
    def documentation = "http://docs.codehaus.org/display/GRAILS/AcegiSecurity+Plugin"

    def watchedResources = "**/grails-app/conf/${APP_CONFIG_FILE}.groovy"    

    def doWithSpring = {
        
        def conf = getConfig()
        
        if (conf) {
            log.debug("Loading Acegi config")

            authenticationHelperService(AuthenticationHelperService){
            }
            
            def loginController = conf.loginController
            def lastIndex = loginController.lastIndexOf(CONTROLLER_SUFFIX)
            assert lastIndex > 0
            loginController = loginController.substring(0,lastIndex)
            loginController = loginController[0].toLowerCase() + loginController.substring(1)
            log.debug("loginController="+loginController)
            
            def authUrl = "/" + loginController + "/" + conf.authenticationAction
            log.debug("authUrl="+authUrl)
            def ajaxAuthUrl = "/" + loginController + "/" + conf.ajaxAuthenticationAction
            log.debug("ajaxAuthUrl="+ajaxAuthUrl)
            def authFailureUrl = "/" + loginController + "/" + conf.authenticationFailureAction
            if(conf.authenticationFailureActionParams!="") {
                authFailureUrl += "?" + conf.authenticationFailureActionParams
            }
            log.debug("authFailureUrl="+authFailureUrl)
            def deniedUrl = null
            if(conf.accessDeniedAction&&conf.accessDeniedAction!="null")
                deniedUrl = "/" + loginController + "/" + conf.accessDeniedAction
            log.debug("deniedUrl="+deniedUrl)
            def ajaxDeniedUrl = null
            if(conf.ajaxAccessDeniedAction&&conf.ajaxAccessDeniedAction!="null")
                ajaxDeniedUrl = "/" + loginController + "/" + conf.ajaxAccessDeniedAction
            log.debug("ajaxDeniedUrl="+ajaxDeniedUrl)
            
            def filters = [
                "httpSessionContextIntegrationFilter",
                "logoutFilter",
                "authenticationProcessingFilter",
                "securityContextHolderAwareRequestFilter",
                "rememberMeProcessingFilter",
                "anonymousProcessingFilter",
                "exceptionTranslationFilter",
                "filterInvocationInterceptor"
             ]

            /** Filter Chain Proxy referenced by Spring Delegating Proxy Filter in web.xml */
            filterChainProxy(org.acegisecurity.util.FilterChainProxy) {
                filterInvocationDefinitionSource = """
					CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
					PATTERN_TYPE_APACHE_ANT
					/**=${filters.join(',')}
					"""
            }

            httpSessionContextIntegrationFilter(org.acegisecurity.context.HttpSessionContextIntegrationFilter) {}

            logoutFilter(org.codehaus.groovy.grails.plugins.acegi.GrailsLogoutFilter,
                "/", ref("rememberMeServices")) {}

            authenticationProcessingFilter(org.acegisecurity.ui.webapp.AuthenticationProcessingFilter) {
                authenticationManager = ref("authenticationManager")
                authenticationFailureUrl = authFailureUrl
                defaultTargetUrl = conf.authenticationRedirectDefaultUrl
                filterProcessesUrl = AUTHENTICATION_FILTER_PROCESSES_URL
                rememberMeServices = ref("rememberMeServices")
            }

            securityContextHolderAwareRequestFilter(org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter) {}

            rememberMeProcessingFilter(org.acegisecurity.ui.rememberme.RememberMeProcessingFilter) {
                authenticationManager = ref("authenticationManager")
                rememberMeServices = ref("rememberMeServices")
            }

            anonymousProcessingFilter(org.acegisecurity.providers.anonymous.AnonymousProcessingFilter) {
                key = conf.anonymousUserKey
                userAttribute = conf.anonymousUserAttribute
            }

            anonymousAuthenticationProvider(org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider) {
                key = conf.anonymousUserKey
            }

            exceptionTranslationFilter(org.acegisecurity.ui.ExceptionTranslationFilter) {
                authenticationEntryPoint = ref("authenticationEntryPoint")
                accessDeniedHandler = ref("accessDeniedHandler")
            }
            
            filterInvocationInterceptor(org.acegisecurity.intercept.web.FilterSecurityInterceptor) {
                authenticationManager = ref("authenticationManager")
                accessDecisionManager = ref("accessDecisionManager")
                if (conf.useRequestMapDomainClass) {
                    objectDefinitionSource = ref("objectDefinitionSource")
                } else {
                    objectDefinitionSource = conf.requestMapDescriptor
                }
            }

            if (conf.useRequestMapDomainClass) {
                objectDefinitionSource(org.codehaus.groovy.grails.plugins.acegi.GrailsFilterInvocationDefinition) {
                    requestMapClass = conf.requestMapClass
                    requestMapPathFieldMethod = createGetter(conf.requestMapPathField)
                    requestMapConfigAttributeFieldMethod = createGetter(conf.requestMapKeywordsField) // "getConfig_attribute"
                    requestMapPathFieldName = conf.requestMapPathField
                }
            }
            
            authenticationManager(org.acegisecurity.providers.ProviderManager) {
                providers = [
                    ref("daoAuthenticationProvider"),
                    ref("anonymousAuthenticationProvider"),
                    ref("rememberMeAuthenticationProvider")]
            }

            daoAuthenticationProvider(org.acegisecurity.providers.dao.DaoAuthenticationProvider) {
                userDetailsService = ref("userDetailsService")
                passwordEncoder = ref("passwordEncoder")
                userCache = ref("userCache")
            }

            userDetailsService(org.codehaus.groovy.grails.plugins.acegi.DomainObjectUserDetailsService) {
                domainClass = conf.userDetailsDomainClass
                usernameField = conf.userDetailsUsernameField
                passwordHashField = conf.userDetailsPasswordHashField
                enabledField = conf.userDetailsEnabledField
                rolesField = conf.userDetailsRolesField
                roleNameField = conf.roleNameField
            }
            
            passwordEncoder(org.acegisecurity.providers.encoding.MessageDigestPasswordEncoder, conf.passwordHashEncodingAlgorithm) {
                encodeHashAsBase64 = conf.encodePasswordHashInBase64
            }
            
            userCache(org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache) {
                cache = ref("cache")
            }
            
            cache(org.springframework.cache.ehcache.EhCacheFactoryBean) {
                cacheManager = ref("cacheManager")
                cacheName = "userCache"
            }

            cacheManager(org.springframework.cache.ehcache.EhCacheManagerFactoryBean) {}

            rememberMeServices(org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices) {
                userDetailsService = ref("userDetailsService")
                cookieName = conf.rememberMeCookie
                key = conf.rememberMeKey
                alwaysRemember = conf.rememberMeAlways
                tokenValiditySeconds = conf.rememberMeExpiration
                parameter = conf.rememberMeParameter
            }

            rememberMeAuthenticationProvider(org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider) {
                key = conf.rememberMeKey
            }

            authenticationEntryPoint(org.codehaus.groovy.grails.plugins.acegi.WithAjaxAuthenticationProcessingFilterEntryPoint) {
                loginFormUrl = authUrl
                forceHttps = conf.forceHttpsForAuthentication
                ajaxLoginFormUrl = ajaxAuthUrl
                if (conf.ajaxHeader) ajaxHeader = conf.ajaxHeader
            }
            
            accessDeniedHandler(org.codehaus.groovy.grails.plugins.acegi.GrailsAccessDeniedHandlerImpl) {
                errorPage = deniedUrl
                ajaxErrorPage = ajaxDeniedUrl
                if (conf.ajaxHeader) ajaxHeader = conf.ajaxHeader
            }

            accessDecisionManager(org.acegisecurity.vote.AffirmativeBased) {
                allowIfAllAbstainDecisions = "false"
                decisionVoters = [ref("roleVoter"),ref("authenticatedVoter")]
            }
            
            roleVoter(org.acegisecurity.vote.RoleVoter) {}
            
            authenticatedVoter(org.acegisecurity.vote.AuthenticatedVoter) {}

            if (conf.useLogger) loggerListener(org.acegisecurity.event.authentication.LoggerListener) {}

            /** experiment on Annotation and MethodSecurityInterceptor .
                *  for secure services
                */
            serviceSecureAnnotation(SecurityAnnotationAttributes) {}
            serviceSecureAnnotationODS(MethodDefinitionAttributes) {
                attributes = ref("serviceSecureAnnotation")
            }
            /** securityInteceptor */
            securityInteceptor(org.codehaus.groovy.grails.plugins.acegi.QuietMethodSecurityInterceptor) {
                validateConfigAttributes = false
                authenticationManager = ref("authenticationManager")
                accessDecisionManager = ref("accessDecisionManager")
                objectDefinitionSource = ref("serviceSecureAnnotationODS")
                throwException = true
            }

            def checkAnnotations = {cls ->
                def result = false
                cls.methods.each {method ->
                    def annotations = method.getAnnotations()
                    if (annotations.size() > 0) {
                        annotations.each {annotation ->
                            if (annotation instanceof Secured) {
                                //println annotation
                                result = true
                            }
                        }
                    }
                }
                return result
            }

            application.serviceClasses.each {serviceClass ->
                if (checkAnnotations(serviceClass.clazz)) {
                    "${serviceClass.propertyName}Sec"(org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator) {
                        beanNames = "${serviceClass.propertyName}"
                        interceptorNames = ["securityInteceptor"]
                        proxyTargetClass = true
                    }
                }
            }

            if (conf.useMail) {
                mailSender(org.springframework.mail.javamail.JavaMailSenderImpl) {
                    host = conf.mailHost
                    username = conf.mailUsername
                    password = conf.mailPassword
                    protocol = conf.mailProtocol
                }
                mailMessage(org.springframework.mail.SimpleMailMessage) {
                    from = conf.mailFrom
                }
            }
        }
    }

    def doWithApplicationContext = {applicationContext ->
        // TODO Implement post initialization spring config (optional)
    }

    def doWithWebDescriptor = {xml ->

        def conf = getConfig()
        if (conf && conf.loadAcegi) {
            def contextParam = xml."context-param"
            contextParam[contextParam.size() - 1] + {
                'filter' {
                    'filter-name'('delegatorToAcegiFilterChainProxy')
                    'filter-class'('org.springframework.web.filter.DelegatingFilterProxy')
                    'init-param' {
                        'param-name'('targetClass')
                        'param-value'('org.acegisecurity.util.FilterChainProxy')
                    }
                }
            }

            def filter = xml."filter"
            filter[filter.size() - 1] + {
                'filter-mapping' {
                    'filter-name'('delegatorToAcegiFilterChainProxy')
                    'url-pattern'("/*")
                }
            }
        }
    }

    def doWithDynamicMethods = {ctx ->
    }

    def onChange = {event ->
    }

    def onApplicationChange = {event ->
    }
    
    def getConfig = {
        ClassLoader parent = getClass().getClassLoader()
        GroovyClassLoader loader = new GroovyClassLoader(parent)

        def appConfigFile = loader.loadClass(APP_CONFIG_FILE)
        def defaultConfigFile = loader.loadClass(DEFAULT_CONFIG_FILE)
        def appConfig = new ConfigSlurper().parse(appConfigFile)
        def defaultConfig = new ConfigSlurper().parse(defaultConfigFile)

        def config = defaultConfig
        if (appConfig) {
            //log.info("Loading "+appConfigFile)
            config = config.merge(appConfig)
        }

        config.acegi
    }
}